\section{Protobuf}
Protocol Buffer (简称Protobuf) 是Google出品的性能优异、跨语言、跨平台的序列化库。2001年初，Protobuf首先在Google内部创建， 我们把它称之为 proto1，一直以来在Google的内部使用，其中也不断的演化，根据使用者的需求也添加很多新的功能，一些内部库依赖它。几乎每个Google的开发者都会使用到它。
Google开始开源它的内部项目时，因为依赖的关系，所以他们决定首先把Protobuf开源出去。 proto1在演化的过程中有些混乱，所以Protobuf的开发者重写了Protobuf的实现，保留了proto1的大部分设计，以及proto1的很多的想法。但是开源的proto2不依赖任何的Google的库，代码也相当的清晰。2008年7月7日，Protobuf开始公布出来。
Protobuf公布出来也得到了大家的广泛的关注， 逐步地也得到了大家的认可，很多项目也采用Protobuf进行消息的通讯，还有基于Protobuf的微服务框架GRPC。在使用的过程中，大家也提出了很多的意见和建议，Protobuf也在演化，于2016年推出了Proto3。 Proto3简化了proto2的开发，提高了开发的效能，但是也带来了版本不兼容的问题。
目前Protobuf的稳定版本是3.9.2，于2019年9月23日发布。由于很多公司很早的就采用了Protobuf，所以很多项目还在使用proto2协议，目前是proto2和proto3同时在使用的状态。Protocol Buffer名称来自于初期一个主要的类的名称ProtocolBuffer。Google当前并没有Protobuf的相关专利，所以不必担心侵权的问题。

\subsection{序列化}
序列化(serialization、marshalling)的过程是指将数据结构或者对象的状态转换成可以存储(比如文件、内存)或者传输的格式(比如网络)。反向操作就是反序列化(deserialization、unmarshalling)的过程。二十世纪九十年代后期，XML开始流行，它是一种人类易读的基于文本的编码方式，易于阅读和理解，但是失去了紧凑的基于字节流的编码的优势。
JSON是一种更轻量级的基于文本的编码方式，经常用在client/server端的通讯中。YAML类似JSON，新的特性更强大，更适合人类阅读，也更紧凑。还有苹果系统的property list。除了上面这些和Protobuf，还有许许多多的序列化格式，比如Thrift、Avro、BSON、CBOR、MessagePack, 还有很多非跨语言的编码格式。项目gosercomp对比了各种go的序列化库，包括序列化和反序列的性能，以及序列化后的数据大小。总体来说Protobuf序列化和反序列的性能都是比较高的，编码后的数据大小也不错。
Protobuf支持很多语言，比如C++、C\#、Dart、Go、Java、Python、Rust等，同时也是跨平台的，所以得到了广泛的应用。Protobuf包含序列化格式的定义、各种语言的库以及一个IDL编译器。正常情况下你需要定义proto文件，然后使用IDL编译器编译成你需要的语言。
\subsection{proto教程}
鉴于官方推荐新代码采用proto3,这个教程主要介绍proto3的开发。官方建议新项目采用proto3，老项目因为兼容性的问题继续使用proto2,并且会长时间的支持proto2。官方的发布日志中列举了proto3的改变：
\begin{itemize}
    \item 移除了原始值字段的出现逻辑。
    \item 移除了required字段
    \item 移除了缺省值
    \item 移除了unknown字段 （3.5中又加上了）
    \item 移除了扩展，使用Any代替
    \item 修复了未知的枚举值的语义
    \item 添加了map类型
    \item 添加了一些标准类似，比如time、动态数据的呈现
    \item 可以使用JSON编码代替二进制proto编码
\end{itemize}
\subsection{一个简单的例子}
首先让我们看一个简单的例子simple.proto:
\begin{protobuf}
    syntax = "proto3";
    message SearchRequest {
            string query = 1;
            int32 page_number = 2;
            int32 result_per_page = 3;
        }
\end{protobuf}
第一行指定protobuf的版本，这里是以proto3格式定义。你还可以指定为proto2。如果没有指定，默认以proto2格式定义。它定义了一个message类型: SearchRequest， 它包含三个字段query、page\_number、result\_per\_page,它会被protoc编译成不同的编程语言的相应对象，比如Java中的class、Go中的struct等。
字段是以[ "repeated" ] type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";"格式定义的。这个例子是一个简单的例子，采用了type fieldName "=" fieldNumber格式定义的。比如第一个字段query, 首先是它的类型string，其次是字段的名称，然后是等号=, 之后是字段的序号，然后是分号。
复杂的结构，前面可以定义为repeated, 序号之后可以定义一些可选项。这是普通的字段定义，当然还有一些复杂的一些字段定义，比如Oneof、Map、Reserved、enum定义，下一节我们再详细讲。在当前的目录下执行:
\mint{proto}{protoc -I=. -I/usr/local/include -I=$(GOPATH)/src --go_out=. simple.proto}, 可以将这个proto编译成Go的代码，因为这里我们使用了go\_out输出格式。

-I指定protoc的搜索import的proto的文件夹。在MacOS操作系统中protobuf把一些扩展的proto放在了/usr/local/include对应的文件夹中，一些第三方的Go库放在了gopath对应的包下，所以这里都把它们加上了。对于这个简单的例子，实际是不需要的。cpp\_out用来生成C++代码，java\_out产生Java代码，python\_out产生python代码，类似地还有csharp\_out、objc\_out、ruby\_out、php\_out等参数。
一些第三方的插件也会定义自己的输出插件，比如gofast\_out使用gogo库生成代码， rust\_out产生rust代码。生成的代码我们指定放在本地文件夹中(--go\_out=.)。

生成的数据结构如下，它还包括一些辅助方法以及GetXXXX读取字段的方法。
\begin{go}
    type SearchRequest struct {
    Query                string   `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"`
    PageNumber           int32    `protobuf:"varint,2,opt,name=page_number,json=pageNumber,proto3" json:"page_number,omitempty"`
    ResultPerPage        int32    `protobuf:"varint,3,opt,name=result_per_page,json=resultPerPage,proto3" json:"result_per_page,omitempty"`
    XXX_NoUnkeyedLiteral struct{} `json:"-"`
    XXX_unrecognized     []byte   `json:"-"`
    XXX_sizecache        int32    `json:"-"`
    }
    func (m *SearchRequest) Reset()         { *m = SearchRequest{} }
    func (m *SearchRequest) String() string { return proto.CompactTextString(m) }
    func (*SearchRequest) ProtoMessage()    {}
    func (*SearchRequest) Descriptor() ([]byte, []int) {
    return fileDescriptor_5ffd045dd4d042c1, []int{0}
    }
    func init() {
            proto.RegisterType((*SearchRequest)(nil), "abc.SearchRequest")
        }
\end{go}
\subsubsection{proto3 格式}
\begin{itemize}
    \item 版本定义,\bashinline{syntax = "proto3"};
    \item 引入其它proto文件
          \begin{protobuf}
              import  "other.proto";
              import public "other2.proto";
              import weak "other.proto";
          \end{protobuf}
          比较少使用的是public和weak关键字。默认情况下weak引入的文件允许不存在(missing)，只为了google内部使用。public具有传递性，如果你在文件中通过public引入第三方的proto文件，那么引入你这个文件同时也会引入第三方的proto。
          我们一般忽略public和weak关键字，这两个关键字也没有在规范中详细进行介绍。

    \item package：定义proto的包名，包名可以避免对message 类型之间的名字冲突，同名的Message可以通过package进行区分。
          在没有为特定语言定义option xxx\_package的时候，它还可以用来生成特定语言的包名，比如Java package, go package。
          \mint{go}{package foo.bar;}
    \item option：option可以用在proto的scope中，或者message、enum、service的定义中。可以是Protobuf定义的option，或者自定义的option。option的定义格式是"option" optionName "=" constant ";",比如:\mint{java}{option java_package = "com.example.foo";}
          一些Protobuf定义的option：
          \begin{itemize}
              \item java\_package
              \item java\_multiple\_files
              \item java\_outer\_classname
              \item optimize\_for
              \item cc\_enable\_arenas
              \item objc\_class\_prefix
              \item deprecated
          \end{itemize}
          自定义的例子：
          \begin{go}
              option (gogoproto.testgen_all) = true;
              option (gogoproto.populate_all) = true;
              option (gogoproto.benchgen_all) = true;
              message NidRepPackedNative {
                      repeated double Field1 = 1 [(gogoproto.nullable) = false, packed = true];
                      repeated float Field2 = 2 [(gogoproto.nullable) = false, packed = true];
                      repeated int32 Field3 = 3 [(gogoproto.nullable) = false, packed = true];
                  }
          \end{go}
          \subsection{普通字段}
          前面讲过，普通字段的格式为field = [ "repeated" ] type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";"
          repeated允许字段重复，对于Go语言来说，它会编译成数组(slice of type)类型的格式。其中类型可以是以下几种类型：
          \begin{itemize}
              \item 数字类型： double、float、int32、int64、uint32、uint64、sint32、sint64: 存储长度可变的浮点数、整数、无符号整数和有符号整数
              \item 存储固定大小的数字类型：fixed32、fixed64、sfixed32、sfixed64: 存储空间固定
              \item 布尔类型: bool
              \item 字符串: string
              \item bytes: 字节数组
              \item messageType: 消息类型
              \item enumType:枚举类型
          \end{itemize}
          字段名、消息名、枚举类型名、map名、服务名等名称首字母必须是字母类型，然后可以是字母、数字或者下划线\_。下面是一个包含各种类型(Scalar Value Types)的proto文件。编译成Go文件
          \begin{bash}
              protoc -I=. -I/usr/local/include -I=$(GOPATH)/src --go_out=. types.proto
          \end{bash}
          proto类型和各语言的对应关系可以参考文档：\href{https://developers.google.com/protocol-buffers/docs/proto3#scalar}{Scalar Value Types}同一个message的每个字段都有唯一一个编号，并且建议终生这个编号都不要改变。

    \item Oneof:如果你有一组字段，同时最多允许这一组中的一个字段出现，就可以使用Oneof定义这一组字段，这有点Union的意思，但是Oneof允许你设置零各值。因为proto3没有办法区分正常的值是否是设置了还是取得缺省值(比如int64类型字段，如果它的值是0，你无法判断数据是否包含这个字段，因为0几可能是数据中设置的值，也可能是这个字段的零值)，所以你可以通过Oneof取得这个功能，因为Oneof有判断字段是否设置的功能。
          \begin{protobuf}
              syntax = "proto3";
              package abc;
              message OneofMessage {
              oneof test_oneof {
              string name = 4;
              int64 value = 9;
              }
              }
          \end{protobuf}
          oneof字段不能同时使用repeated。

    \item map类型
          map类型需要设置键和值的类型，格式是"map" "<" keyType "," type ">" mapName "=" fieldNumber [ "[" fieldOptions "]"。

          比如:
          \begin{protobuf}
              map<int64,string> values = 1;
          \end{protobuf}
          map字段不能同时使用repeated。

    \item Reserved:Reserved可以用来指明此message不使用某些字段，也就是忽略这些字段。可以通过字段编号范围或者字段名称指定保留的字段：
          \begin{protobuf}
              syntax = "proto3";
              package abc;
              message AllNormalypes {
                      reserved 2, 4 to 6;
                      reserved "field14", "field11";
                      double field1 = 1;
                      // float field2 = 2;
                      int32 field3 = 3;
                      // int64 field4 = 4;
                      // uint32 field5 = 5;
                      // uint64 field6 = 6;
                      sint32 field7 = 7;
                      sint64 field8 = 8;
                      fixed32 field9 = 9;
                      fixed64 field10 = 10;
                      // sfixed32 field11 = 11;
                      sfixed64 field12 = 12;
                      bool field13 = 13;
                      // string field14 = 14;
                      bytes field15 = 15;
                  }
          \end{protobuf}
          声明保留的字段你就不要再定义了，否则编译的时候会出错。

    \item 枚举类型
          枚举类型也是常用的一种类型，它限定字段的值只能取某个特定的值，比如星期类型只能取周一到周日七个值。注意枚举类型的定义采用C++ scoping规则，也就是枚举值是枚举类型的兄弟类型，而不是子类型，所以避免在同一个package定义重名的枚举字段。
          \begin{protobuf}
              enum EnumAllowingAlias {
                      option allow_alias = true;
                      UNKNOWN = 0;
                      STARTED = 1;
                      RUNNING = 1;
                  }
              enum EnumNotAllowingAlias {
                      UNKNOWN2 = 0;
                      STARTED2 = 1;
                      // RUNNING = 1;
                  }
          \end{protobuf}
          虽然产生的Go代码会给产生的类型加上前缀，但是proto的定义还是需要避免重名(把上面的STARTED2改成STARTED试试)。
          如果设置allow\_alias，允许字段编号重复，RUNNING是STARTED的别名。枚举的常量必须是一个32比特的整数，从效率的角度考虑，不推荐采用负数。第一个枚举值必须是0，而且必须定义。你也可以把枚举类型定义到message中：
          \begin{protobuf}
              message SearchRequest {
                      string query = 1;
                      int32 page_number = 2;
                      int32 result_per_page = 3;
                      enum Corpus {
                              UNIVERSAL = 0;
                              WEB = 1;
                              IMAGES = 2;
                              LOCAL = 3;
                              NEWS = 4;
                              PRODUCTS = 5;
                              VIDEO = 6;
                          }
                      Corpus corpus = 4;
                  }
          \end{protobuf}
          对于无法识别的枚举值，不同的语言有不同的处理。对于Go语言来说，因为枚举类型以int32来表示，所以对应的值依然用int32解析出来，只不过没有对应的枚举值而已。这种情况还是会存在的，比如proto有改动，或者代码强行设置了一个未知的枚举值。

    \item 使用其它类型
          你也可以使用其它message类型作为字段的类型值。因为前面在介绍字段的类型的时候说了，类型可以是消息类型和枚举类型，枚举类型如上所示，而消息类型如下所示：
          \begin{protobuf}
              message SearchResponse {
                      repeated Result results = 1;
                  }
              message Result {
                      string url = 1;
                      string title = 2;
                      repeated string snippets = 3;
                  }
          \end{protobuf}
          如果要使用的类型在其它proto文件中定义，你需要使用import把对应的文件引入进来。

    \item 嵌套类型
          嵌套类型就是消息类型里面定义了消息类型：
          \begin{protobuf}
              message SearchResponse {
                      message Result {
                              string url = 1;
                              string title = 2;
                              repeated string snippets = 3;
                          }
                      repeated Result results = 1;
                  }
          \end{protobuf}
          如果Result不需要共用，只被SearchResponse使用，可以采用这种定义方式， 如果你需要在外部使用这个类型，其实你也可以使用，但是不如把这个内部的消息类型定义抽取出来，除非你有很特别的含义：
          \begin{protobuf}
              message SomeOtherMessage {
                      SearchResponse.Result result = 1;
                  }
          \end{protobuf}
    \item 未知类型
          未知类型是指数据的格式符合Protobuf的定义，但是数据中的某个/某些字段解析器无法识别的字段类型。一般发生在proto文件有变化，新旧数据不一致的情况导致。proto3最开始对于不能识别的数据就丢弃掉了，但是自3.5 版本后重新引入了未知字段，以匹配proto2的行为。
    \item Any:Any字段允许你处理嵌套数据，并不需要它的proto定义。一个Any以bytes呈现序列化的消息，并且包含一个URL作为这个类型的唯一标识和元数据。为了使用Any类型，你需要引入google/protobuf/any.proto。
          \begin{protobuf}
              import "google/protobuf/any.proto";
              message ErrorStatus {
                      string message = 1;
                      repeated google.protobuf.Any details = 2;
                  }
          \end{protobuf}
          Any类型用来替换proto2中的扩展。

    \item 更新消息类型
          有时候你不得不修改正在使用的proto文件，比如为类型增加一个字段，protobuf支持这种修改而不影响已有的服务，不过你需要遵循一定的规则：
          \begin{itemize}
              \item 不要改变已有字段的字段编号
              \item 当你增加一个新的字段的时候，老系统序列化后的数据依然可以被你的新的格式所解析，只不过你需要处理新加字段的缺省值。 老系统也能解析你信息的值，新加字段只不过被丢弃了
              \item 字段也可以被移除，但是建议你Reserved这个字段，避免将来会使用这个字段
              \item int32, uint32, int64, uint64 和 bool类型都是兼容的
              \item sint32 和 sint64兼容，但是不和其它整数类型兼容
              \item string 和 bytes兼容，如果 bytes 是合法的UTF-8 bytes的话
              \item 嵌入类型和bytes兼容，如果bytes包含一个消息的编码版本的话
              \item fixed32和sfixed32, fixed64和sfixed64
              \item enum和int32, uint32, int64, uint64格式兼容
              \item 把单一一个值改变成一个新的oneof类型的一个成员是安全和二进制兼容的。把一组字段变成一个新的oneof字段也是安全的，如果你确保这一组字段最多只会设置一个。把一个字段移动到一个已存在的oneof字段是不安全的
          \end{itemize}
    \item 其它
          如果没有Any类型，序列化后的数据是没有类型的元数据信息的。这也意味着序列化的包和序列化后的包可以不一样，只要保证数据格式的定义兼容即可。
          还可以通过Protobuf定义grpc服务，不过这个教程不涉及这一方面的介绍。
\end{itemize}
\subsection{Well-Known类型}
除了我们前面介绍的基本类型、Any、还有省略未介绍的Service相关的类型外，还有一些大家常见的类型，Protobuf也提供了定义，比如Timestamp和Duration。这里我们看看如何使用它。

Protobuf提供了github.com/golang/protobuf/ptypes/timestamp.Timestamp和github.com/golang/protobuf/ptypes/duration.Duration两种扩展类型，用来表示时间，并提供了和标准库time.Time和 time.Duration的转换函数。

你可以在你的proto中需要时间戳和duration的地方使用这两个类型，而不是使用标准库的time.Time和time.Duration,因为标注库的类型没有提供protobuf序列化反序列化的功能，你需要额外处理，所以不如直接使用Protobuf提供的对应类型(下一节gogo库提供了对标准库的支持，稍候再讲)。

同时，它还是以指针的方式定义字段，这也意味着我们我们可以分别反序列化的时候，可以区分对应字段是否在数据中存在。
\begin{go}
    package main
    import (
    fmt "fmt"
    "time"
    proto "github.com/golang/protobuf/proto"
    ptypes "github.com/golang/protobuf/ptypes"
    )
    func main() {
    msg := &WellKnownTypes{
    Now:  ptypes.TimestampNow(),
    Took: ptypes.DurationProto(10 * time.Minute),
    }
    data, err := proto.Marshal(msg)
    if err != nil {
            panic(err)
        }
    err = proto.Unmarshal(data, msg)
    if err != nil {
            panic(err)
        }
    fmt.Println(msg)
    }
\end{go}
\subsection{编码}
首先，我们先了解varint方法。varint方法是一种使用变长方式表示整数的方法，可以使用一个或者多个字节来表示小整数和大整数，数越小，使用的字节数越少。

在varint表示的字节中，除了最后一个字节，前面的字节都有一个bit来表示还有字节需要处理，这个标记叫做most significant bit (msb) set。低位放在前面。

比如1表示为0000 0001。最高位0表示这是最后一个字节了，只用一个字节就可以表示。

数字300表示为1010 1100 0000 0010, 两个字节来表示。每个字节高位去掉即可: 010 1100 000 0010,反转000 0010 010 1100,去掉前面的0，也就是100101100, $2^8 + 2^5 + 2^3 + 2^2= 256+32+8+4=300$。Go标准库encoding/binary有对varint处理方法。

事实上。Protobuf是编码的键值对，其中键用varint来表示，其中后三位代表wire type。

Protobuf只定义了6种wire类型。
\begin{table}
    \begin{tabular}{ccc}
        类型 & 含义             & 用于                                                   \\
        0    & Varint           & int32,int64,uint32,uint64,sint32,sint64,bool,enum      \\
        1    & 64-bit           & fixed64,sfixed64,double                                \\
        2    & Length-delimited & string,bytes,embedded message,packaged repeated fields \\
        3    & Start group      & groups(deprecated)                                     \\
        4    & End group        & groups(deprecated)                                     \\
        5    & 32-bit           & fixed32,sfixed32,float
    \end{tabular}
\end{table}
对于字段比较少($2^4=16$)情况，使用一个字节就可以表示key。我们以一个简单的例子，看看Protobuf是如何进行编码的。
这个例子的proto定义文件为:
\begin{protobuf}
    syntax = "proto3";
    option go_package = "main";
    message Person {
            string name = 1;
            int32 id = 2;
            repeated string email = 3;
        }
\end{protobuf}
它定义了Persion这个message，包含三个字段，分别是string, int32和string类型，其中第三个字段允许重复。
定义一个实例：
\begin{protobuf}
    p := &Person{
    Name:  "smallnest",
    Id:    9527,
    Email: []string{"test@example.com"},
    }
\end{protobuf}
编码后是一个32字节的数据：
\begin{table}
    \begin{tabular}{cc}
        oa & 09 73 6d 61 6c 6c 6e 65 73 74                      \\
        10 & b7 4a                                              \\
        1a & 10 74 65 73 74 40 65 78 51 6d 70 6c 65 2e 63 6f 6d
    \end{tabular}
\end{table}
第一个字段的类型是字符串(wire type是2), 字段编号是1 (00000001)， 字段编号左移三位再加上wiretype就是0a(00001010)。

第二个字段的类型是int32(wire type是0), 字段编号是2 (00000010)， 字段编号左移三位再加上wiretype就是10(00010000)。


第三个字段的类型是字符串(wire type是2), 字段编号是3 (00000011)， 字段编号左移三位再加上wiretype就是1a(00011010)。


为了更有效的处理sint32和sint64, Protobuf采用ZigZag编码。

对于固定长度的数字，采用小端序的方式编码（little-endian byte order）。

字符串处理key之外，还需要一个varint记录其长度，然后是UTF-8的字符串值。

嵌入的message和bytes、string一样。

Proto3中对数字类型的repeated字段采用pack处理方式，同一个repeated元素共享同一个key，之后是字段的整体字节长度，然后是各个元素。因为数字类型天生具有可区分性，不需要额外的分隔符进行区分。

\subsection{gogo库}
虽然官方库golang/protobu提供了对Protobuf的支持，但是使用最多还是第三方实现的库gogo/protobuf。
gogo库基于官方库开发，增加了很多的功能，包括：
\begin{itemize}
    \item 快速的序列化和反序列化
    \item 更规范的Go数据结构
    \item goprotobuf兼容
    \item 可选择的产生一些辅助方法，减少使用中的代码输入
    \item 可以选择产生测试代码和benchmark代码
    \item 其它序列化格式
          比如etcd、k8s、dgraph、docker swarmkit都使用它。基于速度和定制化的考虑，gogo有三种产生代码的方式

    \item gofast: 速度优先，不支持其它gogoprotobuf extensions。
          \begin{bash}
              go get github.com/gogo/protobuf/protoc-gen-gofast
              protoc --gofast_out=. myproto.proto
          \end{bash}
          \begin{itemize}
              \item gogofast、gogofaster、gogoslick: 更快的速度、更多的产生代码
              \item gogofast类似gofast,但是会导入gogoprotobuf.
              \item gogofaster类似gogofast, 不会产生XXX\_unrecognized指针字段，可以减少垃圾回收时间。
          \end{itemize}
          gogoslick类似gogofaster,但是可以增加一些额外的方法gostring和equal等等。
          \begin{bash}
              go get github.com/gogo/protobuf/proto
              go get github.com/gogo/protobuf/{binary} //protoc-gen-gogofast、protoc-gen-gogofaster 、protoc-gen-gogoslick
              go get github.com/gogo/protobuf/gogoproto
              protoc -I=. -I=$GOPATH/src -I=$GOPATH/src/github.com/gogo/protobuf/protobuf --{binary}_out=. myproto.proto
          \end{bash}
    \item protoc-gen-gogo: 最快的速度，最多的可定制化
          你可以通过扩展定制序列化: 扩展.
          \begin{bash}
              go get github.com/gogo/protobuf/proto
              go get github.com/gogo/protobuf/jsonpb
              go get github.com/gogo/protobuf/protoc-gen-gogo
              go get github.com/gogo/protobuf/gogoproto
          \end{bash}
          gogo同样支持grpc: \bashinline{protoc --gofast_out=plugins=grpc:. my.proto}, 但是本文不涉及这方面的介绍。

          gogo提供了非常多的option, 以便在产生代码的时候进行更多的控制。这里有一个全面的介绍：extensions，主要包含一些生成快速序列化反序列化的代码的可选项、生成更规范的Go数据结构的可选项、Goprotobuf兼容的可选项，一些产生辅助方法的可选项、产生测试代码和benchmark的可选项，还可以增加jsontag。

          这些可选项既然可以在message、enum中定义，控制单个message的生成方式，也可以在proto file scope中定义，控制这个文件中所有的message和enum的生成方式。如果是文件中定义可选项，可选项后面加\_all下划线, 例如：
          \begin{itemize}
              \item marshaler\_all
              \item sizer\_all
              \item protosizer\_all
              \item unmarshaler\_all
              \item unsafe\_marshaler\_all
              \item unsafe\_unmarshaler\_all
              \item stable\_marshaler\_all
              \item goproto\_enum\_prefix\_all
              \item goproto\_getters\_all
              \item goproto\_stringer\_all
              \item goproto\_enum\_stringer\_all
          \end{itemize}
          经常有网友问，标准库中的time.Time增加进行protobuf序列化？有建议采用unixtime， 使用int64表示。但是如果你使用gogo库，问题迎刃而解，因为它可以为标准库的time.Time和time.Duration生成定制的序列化反序列化方法。你直接使用标准库的类型即可。

          下面是一个比较全面的扩展类型的proto的定义，增加了很多gogo的特别控制。运行 \bashinline{protoc -I=. -I=$(GOPATH)/src --gogo_out=. example.proto} 会产生对应的代码以及测试和benchmark代码，你可以运行测试一下，以便更好的了解gogo库的威力。
\end{itemize}
\begin{protobuf}
    syntax = "proto3";
    package gogo_p3;
    import "github.com/gogo/protobuf/gogoproto/gogo.proto";
    //import "google/protobuf/any.proto";
    import "google/protobuf/duration.proto";
    import "google/protobuf/struct.proto";
    import "google/protobuf/timestamp.proto";
    import "google/protobuf/wrappers.proto";
    option (gogoproto.testgen_all) = true;
    option (gogoproto.populate_all) = true;
    option (gogoproto.benchgen_all) = true;
    option (gogoproto.unmarshaler_all) = false;
    option (gogoproto.marshaler_all) = false;
    option (gogoproto.sizer_all) = true;
    option (gogoproto.equal_all) = true;
    option (gogoproto.verbose_equal_all) = true;
    option (gogoproto.unsafe_marshaler_all) = false;
    option (gogoproto.unsafe_unmarshaler_all) = false;
    message KnownTypes {
            option (gogoproto.compare) = true;
            google.protobuf.Duration dur = 1;
            google.protobuf.Timestamp ts = 2;
            google.protobuf.DoubleValue dbl = 3;
            google.protobuf.FloatValue flt = 4;
            google.protobuf.Int64Value i64 = 5;
            google.protobuf.UInt64Value u64 = 6;
            google.protobuf.Int32Value i32 = 7;
            google.protobuf.UInt32Value u32 = 8;
            google.protobuf.BoolValue bool = 9;
            google.protobuf.StringValue str = 10;
            google.protobuf.BytesValue bytes = 11;
            // TODO uncomment this once https://github.com/gogo/protobuf/issues/197 is fixed
            google.protobuf.Struct st = 12;
            // google.protobuf.Any an = 14;
        }
    message ProtoTypes {
            // TODO this should be a compare_all at the top of the file once time.Time, time.Duration, oneof and map is supported by compare
            option (gogoproto.compare) = true;
            google.protobuf.Timestamp nullableTimestamp = 1;
            google.protobuf.Duration nullableDuration = 2;
            google.protobuf.DoubleValue nullableDouble = 3;
            google.protobuf.FloatValue nullableFloat = 4;
            google.protobuf.Int64Value nullableInt64 = 5;
            google.protobuf.UInt64Value nullableUInt64 = 6;
            google.protobuf.Int32Value nullableInt32 = 7;
            google.protobuf.UInt32Value nullableUInt32 = 8;
            google.protobuf.BoolValue nullableBool = 9;
            google.protobuf.StringValue nullableString = 10;
            google.protobuf.BytesValue nullableBytes = 11;

            google.protobuf.Timestamp timestamp = 12 [(gogoproto.nullable) = false];
            google.protobuf.Duration duration = 13 [(gogoproto.nullable) = false];
            google.protobuf.DoubleValue nonnullDouble = 14 [(gogoproto.nullable) = false];
            google.protobuf.FloatValue nonnullFloat = 15 [(gogoproto.nullable) = false];
            google.protobuf.Int64Value nonnullInt64 = 16 [(gogoproto.nullable) = false];
            google.protobuf.UInt64Value nonnullUInt64 = 17 [(gogoproto.nullable) = false];
            google.protobuf.Int32Value nonnullInt32 = 18 [(gogoproto.nullable) = false];
            google.protobuf.UInt32Value nonnullUInt32 = 19 [(gogoproto.nullable) = false];
            google.protobuf.BoolValue nonnullBool = 20 [(gogoproto.nullable) = false];
            google.protobuf.StringValue nonnullString = 21 [(gogoproto.nullable) = false];
            google.protobuf.BytesValue nonnullBytes = 22 [(gogoproto.nullable) = false];
        }
    message StdTypes {
            google.protobuf.Timestamp nullableTimestamp = 1 [(gogoproto.stdtime) = true];
            google.protobuf.Duration nullableDuration = 2 [(gogoproto.stdduration) = true];
            google.protobuf.DoubleValue nullableDouble = 3 [(gogoproto.wktpointer) = true];;
            google.protobuf.FloatValue nullableFloat = 4 [(gogoproto.wktpointer) = true];;
            google.protobuf.Int64Value nullableInt64 = 5 [(gogoproto.wktpointer) = true];;
            google.protobuf.UInt64Value nullableUInt64 = 6 [(gogoproto.wktpointer) = true];;
            google.protobuf.Int32Value nullableInt32 = 7 [(gogoproto.wktpointer) = true];;
            google.protobuf.UInt32Value nullableUInt32 = 8 [(gogoproto.wktpointer) = true];;
            google.protobuf.BoolValue nullableBool = 9 [(gogoproto.wktpointer) = true];;
            google.protobuf.StringValue nullableString = 10 [(gogoproto.wktpointer) = true];;
            google.protobuf.BytesValue nullableBytes = 11 [(gogoproto.wktpointer) = true];;

            google.protobuf.Timestamp timestamp = 12 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
            google.protobuf.Duration duration = 13 [(gogoproto.stdduration) = true, (gogoproto.nullable) = false];
            google.protobuf.DoubleValue nonnullDouble = 14 [(gogoproto.wktpointer) = true, (gogoproto.nullable) = false];
            google.protobuf.FloatValue nonnullFloat = 15 [(gogoproto.wktpointer) = true, (gogoproto.nullable) = false];
            google.protobuf.Int64Value nonnullInt64 = 16 [(gogoproto.wktpointer) = true, (gogoproto.nullable) = false];
            google.protobuf.UInt64Value nonnullUInt64 = 17 [(gogoproto.wktpointer) = true, (gogoproto.nullable) = false];
            google.protobuf.Int32Value nonnullInt32 = 18 [(gogoproto.wktpointer) = true, (gogoproto.nullable) = false];
            google.protobuf.UInt32Value nonnullUInt32 = 19 [(gogoproto.wktpointer) = true, (gogoproto.nullable) = false];
            google.protobuf.BoolValue nonnullBool = 20 [(gogoproto.wktpointer) = true, (gogoproto.nullable) = false];
            google.protobuf.StringValue nonnullString = 21 [(gogoproto.wktpointer) = true, (gogoproto.nullable) = false];
            google.protobuf.BytesValue nonnullBytes = 22 [(gogoproto.wktpointer) = true, (gogoproto.nullable) = false];
        }
\end{protobuf}
\subsection{Protobuf CMake例子}
项目目录结构如下：
\begin{bash}
    ├── CMakeLists.txt
    ├── main.cc
    └── proto
    └── simple.proto
\end{bash}
CMakeLists.txt文件内容：
\cmakefile{code/protobuf/CMakeLists.txt}
Protobuf文件内容：
\protobuffile{code/protobuf/proto/simple.proto}
这个指南描述了如何使用Protocol buffer 语言去描述你的protocol buffer 数据， 包括 .proto文件符号和如何从.proto文件生成类。包含了proto2版本的protocol buffer语言：对于老版本的proto3 符号，请见Proto2 Language Guide（以及中文译本，抄了很多这里的感谢下老版本的翻译者）

\subsection{定义一个消息类型}
先来看一个非常简单的例子。假设你想定义一个“搜索请求”的消息格式，每一个请求含有一个查询字符串、你感兴趣的查询结果所在的页数，以及每一页多少条查询结果。可以采用如下的方式来定义消息类型的.proto文件了：
\begin{protobuf}
    syntax = "proto3";
    message SearchRequest {
            string query = 1;
            int32 page_number = 2;
            int32 result_per_page = 3;
        }
\end{protobuf}
文件的第一行指定了你正在使用proto3语法：如果你没有指定这个，编译器会使用proto2。这个指定语法行必须是文件的非空非注释的第一个行。
SearchRequest消息格式有3个字段，在消息中承载的数据分别对应于每一个字段。其中每个字段都有一个名字和一种类型。
\subsubsection{指定字段类型}
在上面的例子中，所有字段都是标量类型：两个整型（page\_number和result\_per\_page），一个string类型（query）。当然，你也可以为字段指定其他的合成类型，包括枚举（enumerations）或其他消息类型。

\subsubsection{分配标识号}
正如你所见，在消息定义中，每个字段都有唯一的一个数字标识符。这些标识符是用来在消息的二进制格式中识别各个字段的，一旦开始使用就不能够再改变。注：[1,15]之内的标识号在编码的时候会占用一个字节。[16,2047]之内的标识号则占用2个字节。所以应该为那些频繁出现的消息元素保留 [1,15]之内的标识号。切记：要为将来有可能添加的、频繁出现的标识号预留一些标识号。

最小的标识号可以从1开始，最大到$2^29 - 1$, or 536,870,911。不可以使用其中的[19000－19999]（ (从FieldDescriptor::kFirstReservedNumber 到 FieldDescriptor::kLastReservedNumber)）的标识号， Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号，编译时就会报警。同样你也不能使用早期保留的标识号。

\subsubsection{指定字段规则}
所指定的消息字段修饰符必须是如下之一：
\begin{itemize}
    \item singular：一个格式良好的消息应该有0个或者1个这种字段（但是不能超过1个）。
    \item repeated：在一个格式良好的消息中，这种字段可以重复任意多次（包括0次）。重复的值的顺序会被保留。
\end{itemize}
在proto3中，repeated的标量域默认情况虾使用packed。

你可以了解更多的pakced属性在Protocol Buffer 编码

添加更多消息类型
在一个.proto文件中可以定义多个消息类型。在定义多个相关的消息的时候，这一点特别有用——例如，如果想定义与SearchResponse消息类型对应的回复消息格式的话，你可以将它添加到相同的.proto文件中，如：
\begin{protobuf}
    message SearchRequest {
            string query = 1;
            int32 page_number = 2;
            int32 result_per_page = 3;
        }
    message SearchResponse {
            ...
        }
\end{protobuf}
添加注释
向.proto文件添加注释，可以使用C/C++/Java风格的双斜杠（//） 语法格式，如：
\begin{protobuf}
    message SearchRequest {
            string query = 1;
            int32 page_number = 2;  // Which page number do we want?
            int32 result_per_page = 3;  // Number of results to return per page.
        }
\end{protobuf}
\subsubsection{保留标识符（Reserved）}

如果你通过删除或者注释所有域，以后的用户在更新这个类型的时候可能重用这些标识号。如果你使用旧版本加载相同的.proto文件会导致严重的问题，包括数据损坏、隐私错误等等。现在有一种确保不会发生这种情况的方法就是为字段tag（reserved name可能会JSON序列化的问题）指定reserved标识符，protocol buffer的编译器会警告未来尝试使用这些域标识符的用户。
\begin{protobuf}
    message Foo {
            reserved 2, 15, 9 to 11;
            reserved "foo", "bar";
        }
\end{protobuf}
\begin{remark}
    不要在同一行reserved声明中同时声明域名字和tag number。
\end{remark}
从.proto文件生成了什么？
当用protocol buffer编译器来运行.proto文件时，编译器将生成所选择语言的代码，这些代码可以操作在.proto文件中定义的消息类型，包括获取、设置字段值，将消息序列化到一个输出流中，以及从一个输入流中解析消息。
\begin{itemize}
    \item 对C++来说，编译器会为每个.proto文件生成一个.h文件和一个.cc文件，.proto文件中的每一个消息有一个对应的类。
    \item 对Java来说，编译器为每一个消息类型生成了一个.java文件，以及一个特殊的Builder类（该类是用来创建消息类接口的）。
    \item 对Python来说，有点不太一样——Python编译器为.proto文件中的每个消息类型生成一个含有静态描述符的模块，，该模块与一个元类（metaclass）在运行时（runtime）被用来创建所需的Python数据访问类。
    \item 对go来说，编译器会位每个消息类型生成了一个.pd.go文件。
    \item 对于Ruby来说，编译器会为每个消息类型生成了一个.rb文件。
    \item javaNano来说，编译器输出类似域java但是没有Builder类
    \item 对于Objective-C来说，编译器会为每个消息类型生成了一个pbobjc.h文件和pbobjcm文件，.proto文件中的每一个消息有一个对应的类。
    \item 对于C来说，编译器会为每个消息类型生成了一个.cs文件，.proto文件中的每一个消息有一个对应的类。
          你可以从如下的文档链接中获取每种语言更多API(proto3版本的内容很快就公布)。API Reference
\end{itemize}
\subsubsection{标量数值类型}
一个标量消息字段可以含有一个如下的类型——该表格展示了定义于.proto文件中的类型，以及与之对应的、在自动生成的访问类中定义的类型：
% \begin{table}[H]
% \begin{{tabular}{ccccccc}
% \toprule
% .proto Type&    Notes    &C++ Type&    Java Type&    Python Type[2]&    Go Type&    Ruby Type&    C\# Type&    PHP Type\\
% \midrule
%     double        &&double&    double    &float&    float64    &Float&    double&    float\\
%     float        &&float    &float    &float&    float32    &Float&    float&    float\\
% int32    &使用变长编码，对于负值的效率很低，如果你的域有可能有负值，请使用sint64替代    &int32    &int    &int&    int32    Fixnum 或者 Bignum（根据需要）&    int    integer\\
% uint32&    使用变长编码    &uint32    &int&    int/long&    uint32&    Fixnum 或者 Bignum（根据需要）&    uint    integer\\
% uint64&    使用变长编码    &uint64&    long&    int/long&    uint64&    Bignum&    ulong    &integer/string\\
% sint32&    使用变长编码，这些编码在负值时比int32高效的多&    int32&    int&    int    &int32&    Fixnum 或者 Bignum（根据需要）&    int    integer\\
% sint64&    使用变长编码，有符号的整型值。编码时比通常的int64高效。    &int64    &long&    int/long&    int64    Bignum    &long    integer/string\\
% fixed32    &总是4个字节，如果数值总是比总是比228大的话，这个类型会比uint32高效。&    uint32&    int    &int&    uint32    &Fixnum 或者 Bignum（根据需要）&    uint&    integer\\
% fixed64    &总是8个字节，如果数值总是比总是比256大的话，这个类型会比uint64高效。&    uint64    &long    &int/long&    uint64    &Bignum    &ulong&    integer/string\\
% sfixed32&    总是4个字节    &int32&    int    &int&    int32    &Fixnum 或者 Bignum（根据需要）&    int    in&teger\\
% sfixed64&    总是8个字节&    int64    &long    &int/long&    int64    &Bignum    long&    integer/string\\
% bool&        bool&    boolean&    bool&    bool&    TrueClass/FalseClass&    bool&    boolean\\
% string&    一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。    &string    &String&    str/unicode    &string&    String (UTF-8)&    string&    string\\
% bytes    &可能包含任意顺序的字节数据。&    string&    ByteString&    str    []byte&    String (ASCII-8BIT)    ByteString&    string\\
% \end{tabular}
% \end{table}
你可以在文章Protocol Buffer 编码中，找到更多“序列化消息时各种类型如何编码”的信息。
\begin{enumerate}
    \item 在java中，无符号32位和64位整型被表示成他们的整型对应形式，最高位被储存在标志位中。
    \item 对于所有的情况，设定值会执行类型检查以确保此值是有效。
    \item 64位或者无符号32位整型在解码时被表示成为ilong，但是在设置时可以使用int型值设定，在所有的情况下，值必须符合其设置其类型的要求。
    \item python中string被表示成在解码时表示成unicode。但是一个ASCIIstring可以被表示成str类型。
    \item Integer在64位的机器上使用，string在32位机器上使用
\end{enumerate}
\subsubsection{默认值}
当一个消息被解析的时候，如果被编码的信息不包含一个特定的singular元素，被解析的对象锁对应的域被设置位一个默认值，对于不同类型指定如下：
\begin{itemize}
    \item 对于string，默认是一个空string
    \item 对于bytes，默认是一个空的bytes
    \item 对于bool，默认是false
    \item 对于数值类型，默认是0
    \item 对于枚举，默认是第一个定义的枚举值，必须为0;
    \item 对于消息类型（message），域没有被设置，确切的消息是根据语言确定的，详见generated code guide
    \item 对于可重复域的默认值是空（通常情况下是对应语言中空列表）。
\end{itemize}
\begin{remark}
    对于标量消息域，一旦消息被解析，就无法判断域释放被设置为默认值（例如，例如boolean值是否被设置为false）还是根本没有被设置。你应该在定义你的消息类型时非常注意。例如，比如你不应该定义boolean的默认值false作为任何行为的触发方式。也应该注意如果一个标量消息域被设置为标志位，这个值不应该被序列化传输。
\end{remark}
查看generated code guide选择你的语言的默认值的工作细节。

\subsubsection{枚举}
当需要定义一个消息类型的时候，可能想为一个字段指定某“预定义值序列”中的一个值。例如，假设要为每一个SearchRequest消息添加一个 corpus字段，而corpus的值可能是UNIVERSAL，WEB，IMAGES，LOCAL，NEWS，PRODUCTS或VIDEO中的一个。 其实可以很容易地实现这一点：通过向消息定义中添加一个枚举（enum）并且为每个可能的值定义一个常量就可以了。

在下面的例子中，在消息格式中添加了一个叫做Corpus的枚举类型——它含有所有可能的值 ——以及一个类型为Corpus的字段：

\begin{protobuf}
    message SearchRequest {
            string query = 1;
            int32 page_number = 2;
            int32 result_per_page = 3;
            enum Corpus {
                    UNIVERSAL = 0;
                    WEB = 1;
                    IMAGES = 2;
                    LOCAL = 3;
                    NEWS = 4;
                    PRODUCTS = 5;
                    VIDEO = 6;
                }
            Corpus corpus = 4;
        }
\end{protobuf}
如你所见，Corpus枚举的第一个常量映射为0：每个枚举类型必须将其第一个类型映射为0，这是因为：
\begin{itemize}
    \item 必须有有一个0值，我们可以用这个0值作为默认值。
    \item 这个零值必须为第一个元素，为了兼容proto2语义，枚举类的第一个值总是默认值。
\end{itemize}
你可以通过将不同的枚举常量指定位相同的值。如果这样做你需要将allow\_alias设定位true，否则编译器会在别名的地方产生一个错误信息。

\begin{protobuf}
    enum EnumAllowingAlias {
            option allow_alias = true;
            UNKNOWN = 0;
            STARTED = 1;
            RUNNING = 1;
        }
    enum EnumNotAllowingAlias {
            UNKNOWN = 0;
            STARTED = 1;
            // RUNNING = 1;  // Uncommenting this line will cause a compile error inside Google and a warning message outside.
        }
\end{protobuf}
枚举常量必须在32位整型值的范围内。因为enum值是使用可变编码方式的，对负数不够高效，因此不推荐在enum中使用负数。如上例所示，可以在 一个消息定义的内部或外部定义枚举——这些枚举可以在.proto文件中的任何消息定义里重用。当然也可以在一个消息中声明一个枚举类型，而在另一个不同 的消息中使用它——采用MessageType.EnumType的语法格式。

当对一个使用了枚举的.proto文件运行protocol buffer编译器的时候，生成的代码中将有一个对应的enum（对Java或C++来说），或者一个特殊的EnumDescriptor类（对 Python来说），它被用来在运行时生成的类中创建一系列的整型值符号常量（symbolic constants）。

在反序列化的过程中，无法识别的枚举值会被保存在消息中，虽然这种表示方式需要依据所使用语言而定。在那些支持开放枚举类型超出指定范围之外的语言中（例如C++和Go），为识别的值会被表示成所支持的整型。在使用封闭枚举类型的语言中（Java），使用枚举中的一个类型来表示未识别的值，并且可以使用所支持整型来访问。在其他情况下，如果解析的消息被序列号，未识别的值将保持原样。

关于如何在你的应用程序的消息中使用枚举的更多信息，请查看所选择的语言generated code guide。

\subsubsection{使用其他消息类型}
你可以将其他消息类型用作字段类型。例如，假设在每一个SearchResponse消息中包含Result消息，此时可以在相同的.proto文件中定义一个Result消息类型，然后在SearchResponse消息中指定一个Result类型的字段，如：

\begin{protobuf}
    message SearchResponse {
            repeated Result results = 1;
        }
    message Result {
            string url = 1;
            string title = 2;
            repeated string snippets = 3;
        }
\end{protobuf}
\subsubsection{导入定义}
在上面的例子中，Result消息类型与SearchResponse是定义在同一文件中的。如果想要使用的消息类型已经在其他.proto文件中已经定义过了呢？
你可以通过导入（importing）其他.proto文件中的定义来使用它们。要导入其他.proto文件的定义，你需要在你的文件中添加一个导入声明，如：

\begin{protobuf}
    import "myproject/other_protos.proto";
\end{protobuf}
默认情况下你只能使用直接导入的.proto文件中的定义. 然而， 有时候你需要移动一个.proto文件到一个新的位置， 可以不直接移动.proto文件， 只需放入一个伪 .proto 文件在老的位置， 然后使用import public转向新的位置。import public 依赖性会通过任意导入包含import public声明的proto文件传递。例如：

\begin{protobuf}
    // 这是新的proto
    // All definitions are moved here

    // 这是旧的proto
    // 这是所有客户端正在导入的包
    import public "new.proto";
    import "other.proto";

    // 客户端proto
    import "old.proto";
    // 现在你可以使用新旧两种包的proto定义了。
\end{protobuf}
通过在编译器命令行参数中使用-I/--proto\_pathprotocal 编译器会在指定目录搜索要导入的文件。如果没有给出标志，编译器会搜索编译命令被调用的目录。通常你只要指定proto\_path标志为你的工程根目录就好。并且指定好导入的正确名称就好。

\subsubsection{使用proto2消息类型}
在你的proto3消息中导入proto2的消息类型也是可以的，反之亦然，然后proto2枚举不可以直接在proto3的标识符中使用（如果仅仅在proto2消息中使用是可以的）。

\subsubsection{嵌套类型}
你可以在其他消息类型中定义、使用消息类型，在下面的例子中，Result消息就定义在SearchResponse消息内，如：
\begin{protobuf}
    message SearchResponse {
            message Result {
                    string url = 1;
                    string title = 2;
                    repeated string snippets = 3;
                }
            repeated Result results = 1;
        }
\end{protobuf}
如果你想在它的父消息类型的外部重用这个消息类型，你需要以Parent.Type的形式使用它，如：
\begin{protobuf}
    message SomeOtherMessage {
            SearchResponse.Result result = 1;
        }
\end{protobuf}
当然，你也可以将消息嵌套任意多层，如：
\begin{protobuf}
    message Outer {                  // Level 0
            message MiddleAA {  // Level 1
                    message Inner {   // Level 2
                            int64 ival = 1;
                            bool  booly = 2;
                        }
                }
            message MiddleBB {  // Level 1
                    message Inner {   // Level 2
                            int32 ival = 1;
                            bool  booly = 2;
                        }
                }
        }
\end{protobuf}
\subsubsection{更新一个消息类型}
如果一个已有的消息格式已无法满足新的需求——如，要在消息中添加一个额外的字段——但是同时旧版本写的代码仍然可用。不用担心！更新消息而不破坏已有代码是非常简单的。在更新时只要记住以下的规则即可。
\begin{itemize}
    \item 不要更改任何已有的字段的数值标识。
    \item 如果你增加新的字段，使用旧格式的字段仍然可以被你新产生的代码所解析。你应该记住这些元素的默认值这样你的新代码就可以以适当的方式和旧代码产生的数据交互。相似的，通过新代码产生的消息也可以被旧代码解析：只不过新的字段会被忽视掉。注意，未被识别的字段会在反序列化的过程中丢弃掉，所以如果消息再被传递给新的代码，新的字段依然是不可用的（这和proto2中的行为是不同的，在proto2中未定义的域依然会随着消息被序列化）
    \item 非required的字段可以移除——只要它们的标识号在新的消息类型中不再使用（更好的做法可能是重命名那个字段，例如在字段前添加“OBSOLETE\_”前缀，那样的话，使用的.proto文件的用户将来就不会无意中重新使用了那些不该使用的标识号）。
    \item int32, uint32, int64, uint64,和bool是全部兼容的，这意味着可以将这些类型中的一个转换为另外一个，而不会破坏向前、 向后的兼容性。如果解析出来的数字与对应的类型不相符，那么结果就像在C++中对它进行了强制类型转换一样（例如，如果把一个64位数字当作int32来 读取，那么它就会被截断为32位的数字）。
    \item sint32和sint64是互相兼容的，但是它们与其他整数类型不兼容。
    \item string和bytes是兼容的——只要bytes是有效的UTF-8编码。
    \item 嵌套消息与bytes是兼容的——只要bytes包含该消息的一个编码过的版本。
    \item fixed32与sfixed32是兼容的，fixed64与sfixed64是兼容的。
    \item 枚举类型与int32，uint32，int64和uint64相兼容（注意如果值不相兼容则会被截断），然而在客户端反序列化之后他们可能会有不同的处理方式，例如，未识别的proto3枚举类型会被保留在消息中，但是他的表示方式会依照语言而定。int类型的字段总会保留他们的
\end{itemize}
\subsubsection{Any}
Any类型消息允许你在没有指定他们的.proto定义的情况下使用消息作为一个嵌套类型。一个Any类型包括一个可以被序列化bytes类型的任意消息，以及一个URL作为一个全局标识符和解析消息类型。为了使用Any类型，你需要导入import google/protobuf/any.proto。

\begin{protobuf}
    import "google/protobuf/any.proto";
    message ErrorStatus {
            string message = 1;
            repeated google.protobuf.Any details = 2;
        }
\end{protobuf}
对于给定的消息类型的默认类型URL是type.googleapis.com/packagename.messagename。

不同语言的实现会支持动态库以线程安全的方式去帮助封装或者解封装Any值。例如在java中，Any类型会有特殊的pack()和unpack()访问器，在C++中会有PackFrom()和UnpackTo()方法。
\begin{protobuf}
    // Storing an arbitrary message type in Any.
    NetworkErrorDetails details = ...;
    ErrorStatus status;
    status.add_details()->PackFrom(details);
    // Reading an arbitrary message from Any.
    ErrorStatus status = ...;
    for (const Any& detail : status.details()) {
            if (detail.Is<NetworkErrorDetails>()) {
                    NetworkErrorDetails network_error;
                    detail.UnpackTo(&network_error);
                    ... processing network_error ...
                }
        }
\end{protobuf}
目前，用于Any类型的动态库仍在开发之中
如果你已经很熟悉proto2语法，使用Any替换扩展。

\subsubsection{Oneof}
如果你的消息中有很多可选字段， 并且同时至多一个字段会被设置， 你可以加强这个行为，使用oneof特性节省内存.

Oneof字段就像可选字段， 除了它们会共享内存， 至多一个字段会被设置。 设置其中一个字段会清除其它字段。 你可以使用case()或者WhichOneof() 方法检查哪个oneof字段被设置， 看你使用什么语言了.

\subsubsection{使用Oneof}
为了在.proto定义Oneof字段， 你需要在名字前面加上oneof关键字, 比如下面例子的test\_oneof:
\begin{protobuf}
    message SampleMessage {
    oneof test_oneof {
    string name = 4;
    SubMessage sub_message = 9;
    }
    }
\end{protobuf}
然后你可以增加oneof字段到 oneof 定义中. 你可以增加任意类型的字段, 但是不能使用repeated 关键字.

在产生的代码中, oneof字段拥有同样的 getters 和setters， 就像正常的可选字段一样. 也有一个特殊的方法来检查到底那个字段被设置. 你可以在相应的语言API指南中找到oneof API介绍.

\subsubsection{Oneof 特性}
设置oneof会自动清楚其它oneof字段的值. 所以设置多次后，只有最后一次设置的字段有值.
\begin{protobuf}
    SampleMessage message;
    message.set_name("name");
    CHECK(message.has_name());
    message.mutable_sub_message();   // Will clear name field.
    CHECK(!message.has_name());
\end{protobuf}
\begin{itemize}
    \item 如果解析器遇到同一个oneof中有多个成员，只有最会一个会被解析成消息。
    \item oneof不支持repeated.
    \item 反射API对oneof 字段有效.
    \item 如果使用C++,需确保代码不会导致内存泄漏. 下面的代码会崩溃， 因为sub\_message 已经通过set\_name()删除了
\end{itemize}
\begin{protobuf}
    SampleMessage message;
    SubMessage* sub_message = message.mutable_sub_message();
    message.set_name("name");      // Will delete sub_message
    sub_message->set_...            // Crashes here
\end{protobuf}
在C++中，如果你使用Swap()两个oneof消息，每个消息，两个消息将拥有对方的值，例如在下面的例子中，msg1会拥有sub\_message并且msg2会有name。
\begin{protobuf}
    SampleMessage msg1;
    msg1.set_name("name");
    SampleMessage msg2;
    msg2.mutable_sub_message();
    msg1.swap(&msg2);
    CHECK(msg1.has_sub_message());
    CHECK(msg2.has_name());
\end{protobuf}
向后兼容性问题
当增加或者删除oneof字段时一定要小心. 如果检查oneof的值返回None/NOT\_SET, 它意味着oneof字段没有被赋值或者在一个不同的版本中赋值了。 你不会知道是哪种情况，因为没有办法判断如果未识别的字段是一个oneof字段。

Tag 重用问题：
\begin{itemize}
    \item 将字段移入或移除oneof：在消息被序列号或者解析后，你也许会失去一些信息（有些字段也许会被清除）
    \item 删除一个字段或者加入一个字段：在消息被序列号或者解析后，这也许会清除你现在设置的oneof字段
    \item 分离或者融合oneof：行为与移动常规字段相似。
\end{itemize}

\subsubsection{Map}
如果你希望创建一个关联映射，protocol buffer提供了一种快捷的语法：
\begin{protobuf}
    map<key_type, value_type> map_field = N;
\end{protobuf}
其中key\_type可以是任意Integer或者string类型（所以，除了floating和bytes的任意标量类型都是可以的）value\_type可以是任意类型。

例如，如果你希望创建一个project的映射，每个Projecct使用一个string作为key，你可以像下面这样定义：
\begin{protobuf}
    map<string, Project> projects = 3;
\end{protobuf}
Map的字段可以是repeated。
序列化后的顺序和map迭代器的顺序是不确定的，所以你不要期望以固定顺序处理Map
当为.proto文件产生生成文本格式的时候，map会按照key 的顺序排序，数值化的key会按照数值排序。
从序列化中解析或者融合时，如果有重复的key则后一个key不会被使用，当从文本格式中解析map时，如果存在重复的key。
生成map的API现在对于所有proto3支持的语言都可用了，你可以从API指南找到更多信息。

向后兼容性问题
map语法序列化后等同于如下内容，因此即使是不支持map语法的protocol buffer实现也是可以处理你的数据的：

\begin{protobuf}
    message MapFieldEntry {
            key_type key = 1;
            value_type value = 2;
        }
    repeated MapFieldEntry map_field = N;
\end{protobuf}
Package
当然可以为.proto文件新增一个可选的package声明符，用来防止不同的消息类型有命名冲突。如：
\begin{protobuf}
    package foo.bar;
    message Open { ... }
\end{protobuf}
在其他的消息格式定义中可以使用包名+消息名的方式来定义域的类型，如：

\begin{protobuf}
    message Foo {
            ...
            required foo.bar.Open open = 1;
            ...
        }
\end{protobuf}
包的声明符会根据使用语言的不同影响生成的代码。
\begin{itemize}
    \item 对于C++，产生的类会被包装在C++的命名空间中，如上例中的Open会被封装在 foo::bar空间中； - 对于Java，包声明符会变为java的一个包，除非在.proto文件中提供了一个明确有java\_package；
    \item 对于 Python，这个包声明符是被忽略的，因为Python模块是按照其在文件系统中的位置进行组织的。
    \item 对于Go，包可以被用做Go包名称，除非你显式的提供一个option go\_package在你的.proto文件中。
    \item 对于Ruby，生成的类可以被包装在内置的Ruby名称空间中，转换成Ruby所需的大小写样式 （首字母大写；如果第一个符号不是一个字母，则使用PB\_前缀），例如Open会在Foo::Bar名称空间中。
    \item 对于javaNano包会使用Java包，除非你在你的文件中显式的提供一个option java\_package。
    \item 对于C\#包可以转换为PascalCase后作为名称空间，除非你在你的文件中显式的提供一个option csharp\_namespace，例如，Open会在Foo.Bar名称空间中
\end{itemize}
包及名称的解析
Protocol buffer语言中类型名称的解析与C++是一致的：首先从最内部开始查找，依次向外进行，每个包会被看作是其父类包的内部类。当然对于 （foo.bar.Baz）这样以“.”分隔的意味着是从最外围开始的。

ProtocolBuffer编译器会解析.proto文件中定义的所有类型名。 对于不同语言的代码生成器会知道如何来指向每个具体的类型，即使它们使用了不同的规则。

定义服务(Service)
如果想要将消息类型用在RPC(远程方法调用)系统中，可以在.proto文件中定义一个RPC服务接口，protocol buffer编译器将会根据所选择的不同语言生成服务接口代码及存根。如，想要定义一个RPC服务并具有一个方法，该方法能够接收 SearchRequest并返回一个SearchResponse，此时可以在.proto文件中进行如下定义：

\begin{protobuf}
    service SearchService {
            rpc Search (SearchRequest) returns (SearchResponse);
        }
\end{protobuf}
最直观的使用protocol buffer的RPC系统是gRPC,一个由谷歌开发的语言和平台中的开源的PRC系统，gRPC在使用protocl buffer时非常有效，如果使用特殊的protocol buffer插件可以直接为您从.proto文件中产生相关的RPC代码。

如果你不想使用gRPC，也可以使用protocol buffer用于自己的RPC实现，你可以从proto2语言指南中找到更多信息

还有一些第三方开发的PRC实现使用Protocol Buffer。参考第三方插件wiki查看这些实现的列表。

JSON 映射
Proto3 支持JSON的编码规范，使他更容易在不同系统之间共享数据，在下表中逐个描述类型。

如果JSON编码的数据丢失或者其本身就是null，这个数据会在解析成protocol buffer的时候被表示成默认值。如果一个字段在protocol buffer中表示为默认值，体会在转化成JSON的时候编码的时候忽略掉以节省空间。具体实现可以提供在JSON编码中可选的默认值。
\begin{table}[H]
    \begin{tabular}{cccc}
        \toprule
        proto3                 & JSON                                   & JSON示例                                                                                                                                                       & 注意                                                                                                                         \\
        \midrule
        message                & object                                 & {“fBar”: v, “g”: null, …}                                                                                                                                      & 产生JSON对象，消息字段名可以被映射成lowerCamelCase形式，并且成为JSON对象键，null被接受并成为对应字段的默认值                 \\
        enum                   & string                                 & “FOO\_BAR”                                                                                                                                                     & 枚举值的名字在proto文件中被指定                                                                                              \\
        map                    & object                                 & {“k”: v, …}                                                                                                                                                    & 所有的键都被转换成string                                                                                                     \\
        repeated               & V    array    [v, …]                   & null被视为空列表                                                                                                                                                                                                                                                                              \\
        bool                   & true, false                            & true, false                                                                                                                                                                                                                                                                                   \\
        string                 & string                                 & “Hello World!”                                                                                                                                                 &                                                                                                                              \\
        bytes                  & base64                                 & string                                                                                                                                                         & “YWJjMTIzIT8kKiYoKSctPUB+”                                                                                                   \\
        int32, fixed32, uint32 & number                                 & 1, -10, 0                                                                                                                                                      & JSON值会是一个十进制数，数值型或者string类型都会接受                                                                         \\
        int64, fixed64, uint64 & string                                 & “1”, “-10”                                                                                                                                                     & JSON值会是一个十进制数，数值型或者string类型都会接受                                                                         \\
        float, double          & number                                 & 1.1, -10.0, 0, “NaN”, “Infinity”                                                                                                                               & JSON值会是一个数字或者一个指定的字符串如”NaN”,”infinity”或者”-Infinity”，数值型或者字符串都是可接受的，指数符号也可以接受    \\
        Any                    & object    {“@type”: “url”, “f”: v, … } & 如果一个Any保留一个特上述的JSON映射，则它会转换成一个如下形式：{"@type": xxx, "value": yyy}否则，该值会被转换成一个JSON对象，@type字段会被插入所指定的确定的值                                                                                                                                \\
        Timestamp              & string                                 & “1972-01-01T10:00:20.021Z”                                                                                                                                     & 使用RFC 339，其中生成的输出将始终是Z-归一化啊的，并且使用0，3，6或者9位小数                                                  \\
        Duration               & string                                 & “1.000340012s”, “1s”                                                                                                                                           & 生成的输出总是0，3，6或者9位小数，具体依赖于所需要的精度，接受所有可以转换为纳秒级的精度                                     \\
        Struct                 & object    { … }                        & 任意的JSON对象，见struct.proto                                                                                                                                                                                                                                                                \\
        Wrapper                & types                                  & various types                                                                                                                                                  & 2, “2”, “foo”, true, “true”, null, 0, …    包装器在JSON中的表示方式类似于基本类型，但是允许nulll，并且在转换的过程中保留null \\
        FieldMask              & string                                 & “f.fooBar,h”                                                                                                                                                   & 见fieldmask.proto                                                                                                            \\
        ListValue              & array                                  & [foo, bar, …]                                                                                                                                                  &                                                                                                                              \\
        Value                  & value                                  &                                                                                                                                                                & 任意JSON值                                                                                                                   \\
        NullValue              & null                                   &                                                                                                                                                                & JSON null                                                                                                                    \\
        \toprule
    \end{tabular}
\end{table}
\subsubsection{选项}
定义.proto文件时能够标注一系列的option。Option并不改变整个文件声明的含义，但却能够影响特定环境下处理方式。完整的可用选项可以在google/protobuf/descriptor.proto找到。

一些选项是文件级别的，意味着它可以作用于最外范围，不包含在任何消息内部、enum或服务定义中。一些选项是消息级别的，意味着它可以用在消息定义的内部。当然有些选项可以作用在域、enum类型、enum值、服务类型及服务方法中。到目前为止，并没有一种有效的选项能作用于所有的类型。

如下就是一些常用的选项：
\begin{itemize}
    \item java\_package (文件选项) :这个选项表明生成java类所在的包。如果在.proto文件中没有明确的声明java\_package，就采用默认的包名。当然了，默认方式产生的 java包名并不是最好的方式，按照应用名称倒序方式进行排序的。如果不需要产生java代码，则该选项将不起任何作用。如：
          \begin{protobuf}
              option java_package = "com.example.foo";
          \end{protobuf}
    \item java\_outer\_classname (文件选项): 该选项表明想要生成Java类的名称。如果在.proto文件中没有明确的java\_outer\_classname定义，生成的class名称将会根据.proto文件的名称采用驼峰式的命名方式进行生成。如（foo\_bar.proto生成的java类名为FooBar.java）,如果不生成java代码，则该选项不起任何作用。如：
          \begin{protobuf}
              option java_outer_classname = "Ponycopter";
          \end{protobuf}
    \item optimize\_for(文件选项): 可以被设置为 SPEED, CODE\_SIZE,或者LITE\_RUNTIME。这些值将通过如下的方式影响C++及java代码的生成：
          \begin{itemize}
              \item SPEED (default): protocol buffer编译器将通过在消息类型上执行序列化、语法分析及其他通用的操作。这种代码是最优的。
              \item CODE\_SIZE: protocol buffer编译器将会产生最少量的类，通过共享或基于反射的代码来实现序列化、语法分析及各种其它操作。采用该方式产生的代码将比SPEED要少得多， 但是操作要相对慢些。当然实现的类及其对外的API与SPEED模式都是一样的。这种方式经常用在一些包含大量的.proto文件而且并不盲目追求速度的 应用中。
              \item LITE\_RUNTIME: protocol buffer编译器依赖于运行时核心类库来生成代码（即采用libprotobuf-lite 替代libprotobuf）。这种核心类库由于忽略了一 些描述符及反射，要比全类库小得多。这种模式经常在移动手机平台应用多一些。编译器采用该模式产生的方法实现与SPEED模式不相上下，产生的类通过实现 MessageLite接口，但它仅仅是Messager接口的一个子集。
          \end{itemize}
          \begin{protobuf}
              option optimize_for = CODE_SIZE;
          \end{protobuf}
    \item cc\_enable\_arenas(文件选项):对于C++产生的代码启用arena allocation
    \item objc\_class\_prefix(文件选项):设置Objective-C类的前缀，添加到所有Objective-C从此.proto文件产生的类和枚举类型。没有默认值，所使用的前缀应该是苹果推荐的3-5个大写字符，注意2个字节的前缀是苹果所保留的。
    \item deprecated(字段选项):如果设置为true则表示该字段已经被废弃，并且不应该在新的代码中使用。在大多数语言中没有实际的意义。在java中，这回变成@Deprecated注释，在未来，其他语言的代码生成器也许会在字标识符中产生废弃注释，废弃注释会在编译器尝试使用该字段时发出警告。如果字段没有被使用你也不希望有新用户使用它，尝试使用保留语句替换字段声明。
\end{itemize}
\begin{protobuf}
    int32 old_field = 6 [deprecated=true];
\end{protobuf}
\subsubsection{自定义选项}
ProtocolBuffers允许自定义并使用选项。该功能应该属于一个高级特性，对于大部分人是用不到的。如果你的确希望创建自己的选项，请参看 Proto2 Language Guide。注意创建自定义选项使用了拓展，拓展只在proto3中可用。
\subsubsection{生成访问类}
可以通过定义好的.proto文件来生成Java,Python,C++, Ruby, JavaNano, Objective-C,或者C\# 代码，需要基于.proto文件运行protocol buffer编译器protoc。如果你没有安装编译器，下载安装包并遵照README安装。对于Go,你还需要安装一个特殊的代码生成器插件。你可以通过GitHub上的protobuf库找到安装过程

通过如下方式调用protocol编译器：
\begin{bash}
    protoc --proto_path=IMPORT_PATH --cpp_out=DST_DIR --java_out=DST_DIR --python_out=DST_DIR --go_out=DST_DIR --ruby_out=DST_DIR --javanano_out=DST_DIR --objc_out=DST_DIR --csharp_out=DST_DIR path/to/file.proto
\end{bash}
IMPORT\_PATH声明了一个.proto文件所在的解析import具体目录。如果忽略该值，则使用当前目录。如果有多个目录则可以多次调用--proto\_path，它们将会顺序的被访问并执行导入。-I=IMPORT\_PATH是--proto\_path的简化形式。
当然也可以提供一个或多个输出路径：
\begin{itemize}
    \item --cpp\_out 在目标目录DST\_DIR中产生C++代码，可以在C++代码生成参考中查看更多。
    \item --java\_out 在目标目录DST\_DIR中产生Java代码，可以在 Java代码生成参考中查看更多。
    \item --python\_out 在目标目录 DST\_DIR 中产生Python代码，可以在Python代码生成参考中查看更多。
    \item --go\_out 在目标目录 DST\_DIR 中产生Go代码，可以在GO代码生成参考中查看更多。
    \item --ruby\_out在目标目录 DST\_DIR 中产生Ruby代码，参考正在制作中。
    \item --javanano\_out在目标目录DST\_DIR中生成JavaNano，JavaNano代码生成器有一系列的选项用于定制自定义生成器的输出：你可以通过生成器的README查找更多信息，JavaNano参考正在制作中。
    \item --objc\_out在目标目录DST\_DIR中产生Object代码，可以在Objective-C代码生成参考中查看更多。
    \item --csharp\_out在目标目录DST\_DIR中产生Object代码，可以在C\#代码生成参考中查看更多。
    \item --php\_out在目标目录DST\_DIR中产生Object代码，可以在PHP代码生成参考中查看更多。
\end{itemize}
作为一个方便的拓展，如果DST\_DIR以.zip或者.jar结尾，编译器会将输出写到一个ZIP格式文件或者符合JAR标准的.jar文件中。注意如果输出已经存在则会被覆盖，编译器还没有智能到可以追加文件。
你必须提议一个或多个.proto文件作为输入，多个.proto文件可以只指定一次。虽然文件路径是相对于当前目录的，每个文件必须位于其IMPORT\_PATH下，以便每个文件可以确定其规范的名称。
